#pragma once

#include "slice.h"

namespace sfc::str {

using option::Option;
using slice::Slice;
using tuple::Tuple;

using cstr_t = const char*;

template <class T>
struct Pattern;

template <class P>
struct Split;

template <class T>
struct FromStr;

struct Str {
  Slice<const char> _inn;

  // field
  using Base::_len;
  using Base::_ptr;

  // property
  using Base::is_empty;
  using Base::is_null;
  using Base::len;
  using Base::ptr;

  [[sfc_inline]] constexpr Str() noexcept = default;

  [[sfc_inline]] constexpr explicit Str(Base other) noexcept : Base{other} {}

  [[sfc_inline]] constexpr Str(const char* p, usize n) noexcept : Base{p, n} {}

  template <usize N>
  [[sfc_inline]] constexpr Str(const char (&s)[N]) noexcept : Base{s, N - 1} {}

  [[sfc_inline]] Str(const char* p) noexcept : Base{p, __builtin_object_size(p, 0)} {
    if (_len == usize(-1)) {
      _len = __builtin_strlen(p);
    }
  }

  [[sfc_inline]] static auto from_cstr(const char* p) -> Str {
    return Str{p, __builtin_strlen(p)};
  }

  [[sfc_inline]] auto as_bytes() const -> Slice<const u8> {
    return {reinterpret_cast<const u8*>(_ptr), _len};
  }

  [[sfc_inline]] auto operator[](auto idx) const -> char {
    return Base::operator[](idx);
  }

  [[sfc_inline]] auto operator()(auto i0, auto i1) const -> Str {
    return Str{Base::operator[](ids)};
  }

  [[sfc_inline]] auto split_at(auto mid) const -> Tuple<Str, Str> {
    const auto x = Base::split_at(mid);
    return {Str{x._0}, Str{x._1}};
  };

  auto operator==(Str other) const -> bool {
    if (_len != other._len) return false;
    if (_ptr == other._ptr) return true;
    return __builtin_memcmp(_ptr, other._ptr, _len) == 0;
  }

  auto find(auto&& pat) const -> Option<usize> {
    auto s = Pattern{mem::move(pat)}.searcher(*this);
    return s.next_match();
  }

  auto rfind(auto&& pat) const -> Option<usize> {
    auto s = Pattern{mem::move(pat)}.searcher(*this);
    return s.next_match_back();
  }

  auto starts_with(auto&& pat) const -> bool {
    auto s = Pattern{mem::move(pat)}.searcher(*this);
    return s.next().match;
  }

  auto ends_with(auto&& pat) const -> bool {
    auto s = Pattern{mem::move(pat)}.searcher(*this);
    return s.next_back().match;
  }

  auto contains(auto&& pat) const -> bool {
    auto s = Pattern{mem::move(pat)}.searcher(*this);
    return s.next_match();
  }

  auto split(auto&& pat) const {
    return Pattern{mem::move(pat)}.split(*this);
  }

  void format(auto& f) const {
    f.pad(*this);
  }

  template <class T>
  auto parse() const -> Option<T> {
    return FromStr<T>::from_str(*this);
  }
};

template <usize N>
struct CStr {
  char _buf[N];

  [[sfc_inline]] CStr() noexcept {
    _buf[0] = 0;
  }

  [[sfc_inline]] CStr(Str s) {
    const auto cnt = cmp::min(N - 1, s.len());
    ptr::copy(s._ptr, _buf, cnt);
    _buf[cnt] = 0;
  }

  [[sfc_inline]] operator cstr_t() const noexcept {
    return _buf;
  }
};

struct SearchStep : Option<usize> {
  usize end = 0;
  bool match = false;
};

template <class P>
struct Searcher : Str {
  using Str::_len;
  using Str::_ptr;

  P _pat;
  usize _finger = 0;
  usize _finger_back = _len;

  auto next() -> SearchStep {
    if (_finger + 1 > _len) {
      return {};
    }
    const auto res = _pat((*this)[_finger++]);
    return {_finger - 1, _finger, res};
  }

  auto next_back() -> SearchStep {
    if (_finger_back == 0) {
      return {};
    }
    const auto res = _pat((*this)[--_finger_back]);
    return {_finger_back, _finger_back + 1, res};
  }

  auto next_match() -> Option<usize> {
    while (auto idx = this->next()) {
      if (idx.match) {
        return idx;
      }
    }
    return {};
  }

  auto next_match_back() -> Option<usize> {
    while (auto idx = this->next_back()) {
      if (idx.match) {
        return idx;
      }
    }
    return {};
  }

  auto next_reject() -> Option<usize> {
    while (auto idx = this->next()) {
      if (!idx.match) {
        return idx;
      }
    }
    return {};
  }

  auto next_reject_back() -> Option<usize> {
    while (auto idx = this->next_back()) {
      if (!idx.match) {
        return idx;
      }
    }
    return {};
  }
};

template <>
inline auto Searcher<char>::next() -> SearchStep {
  if (_finger + 1 > _len) {
    return {};
  }
  _finger += 1;
  const auto m = _pat == (*this)[_finger - 1];
  return {_finger - 1, _finger, m};
}

template <>
inline auto Searcher<char>::next_back() -> SearchStep {
  if (_finger_back == 0) {
    return {};
  }
  _finger_back -= 1;
  const auto m = _pat == (*this)[_finger_back];
  return {_finger_back, _finger_back + 1, m};
}

template <>
inline auto Searcher<Str>::next() -> SearchStep {
  if (_finger == _len) {
    return {};
  }
  const auto s = (*this)[{_finger, _finger + _pat._len}];
  const auto m = s == _pat;
  _finger += s._len;
  return {_finger - s._len, _finger, m};
}

template <>
inline auto Searcher<Str>::next_back() -> SearchStep {
  if (_finger_back == 0) {
    return {};
  }
  const auto s = (*this)[{isize(_finger_back) - _pat._len, _finger_back}];
  const auto m = s == _pat;
  _finger_back -= s._len;
  return {_finger_back, _finger_back + s._len, m};
}

template <class T>
struct Pattern : T {
  auto searcher(Str s) -> Searcher<T> {
    return {s, static_cast<T&&>(*this)};
  }

  auto split(Str s) -> Split<T> {
    return {{}, this->searcher(s)};
  }
};

template <class P>
Pattern(P) -> Pattern<P>;

template <usize N>
Pattern(const char (&)[N]) -> Pattern<Str>;

Pattern(char)->Pattern<char>;

template <>
struct Pattern<char> {
  char _0;
  auto searcher(Str s) const -> Searcher<char> {
    return {s, _0};
  }
};

template <class P>
struct Split : iter::Iterator<Split<P>, Str> {
  Searcher<P> _0;

  auto next() -> Option<Str> {
    const auto a = _0._finger;
    if (a == _0._len) {
      return {};
    }
    const auto b = _0.next_match() || _0._len;
    return _0[{a, b}];
  }

  auto next_back() -> Option<Str> {
    const auto b = _0._finger_back;
    if (b == 0) {
      return {};
    }
    const auto a = _0.next_match_back();
    if (a.is_none()) {
      return _0[{_, b}];
    }
    return _0[{_0._finger_back + 1, b}];
  }
};

template <class T>
struct FromStr {
  static auto from_str(Str s) -> Option<T>;
};

template <class T>
requires(__is_class(T)) struct FromStr<T> : T {
  static auto from_str(Str s) -> Option<T>;
};

template <auto>
auto type_name() -> Str {
  static const auto s = Str(__PRETTY_FUNCTION__);
  return s;
}

template <auto>
auto enum_name() -> Str {
  static const auto s = Str(__PRETTY_FUNCTION__);
  return s;
}

}  // namespace sfc::str
